package com.imooc.reader.management.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.reader.management.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;

// spring security配置类
@Configuration
public class SecurityConfiguration {

    /**
     * 认证成功后的回调函数，显示对应成功的JSON数据
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("code", "0");
            map.put("message", "success");
            // 设置返回的内容转化为JSON格式
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(map);
            PrintWriter printWriter = response.getWriter();
            printWriter.write(json);
            printWriter.flush();
            printWriter.close();
        }));
    }

    /**
     * 认证失败后的回调函数，显示对应成功的JSON数据
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (((request, response, exception) -> {
            response.setContentType("application/json;charset=utf-8");
            // 设置错误码
            response.setStatus(401);
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("code", exception.getMessage());
            String message = null;
            switch (exception.getMessage()) {
                case "INCORRECT_USERNAME_OR_PASSWORD" -> message = "用户名或密码错误";
                case "THE_USER_HAS_BEEN_FROZEN" -> message = "该用户已被冻结";
            }
            map.put("message", message);
            // 设置返回的内容转化为JSON格式
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(map);
            PrintWriter printWriter = response.getWriter();
            printWriter.write(json);
            printWriter.flush();
            printWriter.close();
        }));
    }

    /**
     * 访问未经认证的资源的回调函数
     * @return
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (((request, response, authException) -> {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(401);
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("code", "UNAUTHORIZED_ACCESS_IS_DENIED");
            map.put("message", "未经认证拒绝访问");
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(map);
            PrintWriter writer = response.getWriter();
            writer.write(json);
            writer.flush();
            writer.close();
        }));
    }

    /**
     * 注销成功后的回调函数，显示对应成功的JSON数据
     * @return
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("code", "0");
            map.put("message", "success");
            // 设置返回的内容转化为JSON格式
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(map);
            PrintWriter printWriter = response.getWriter();
            printWriter.write(json);
            printWriter.flush();
            printWriter.close();
        }));
    }

    /**
     * 过滤选项基本配置的bean
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 设置session保存策略，将session会话信息保存在服务器上
        http.sessionManagement(sessionManagementCustomizer -> sessionManagementCustomizer.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED));

        // 配置iframe显示内容
        http.headers(headersConfigurer -> headersConfigurer.frameOptions(frameOptionsConfig -> frameOptionsConfig.disable()));

        // 设置异常情况下的回调函数
        http.exceptionHandling(exceptionHandlingConfigurer -> exceptionHandlingConfigurer.authenticationEntryPoint(authenticationEntryPoint()));

        // 静态资源给予放行，其他的资源需要授权认证
        http.authorizeHttpRequests(authz -> authz.requestMatchers("/assets/**", "/**.ico", "/images/**").permitAll()
                .anyRequest().authenticated());

        // 配置登录页面为放行的，设置登录的用户名、密码
        http.formLogin(formLoginConfigurer -> formLoginConfigurer.loginProcessingUrl("/authentication")
                .successHandler(authenticationSuccessHandler()) // 设置认证成功的回调函数
                .failureHandler(authenticationFailureHandler()) // 设置认证失败的回调函数
                .loginPage("/login.html") // 设置登录页
                .permitAll()
                .usernameParameter("username")
                .passwordParameter("password"));

        // 用户注销行为相关配置
        http.logout(logoutConfigurer -> logoutConfigurer.logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler())
                .invalidateHttpSession(true) // 使session失效
                .clearAuthentication(true)); // 清除认证数据，兼容服务器端保存用户信息使用非session方式

        http.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable());
        return http.build();
    }

    /**
     * 根据用户名和密码进行验证
     * @param userService
     * @return
     */
    @Bean
    public AuthenticationProvider authenticationProvider(UserService userService) {
        return new AuthenticationProvider() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                String username = authentication.getName(); // 用户名
                String password = authentication.getCredentials().toString(); // 密码
                UserDetails userDetails = userService.loadUserByUsername(username); // 先判断用户名是否存在
                PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); // 配置BCrypt加密算法
                if (passwordEncoder.matches(password, userDetails.getPassword())) {
                    // 用户是否被冻结
                    if (!userDetails.isEnabled()) {
                        throw new DisabledException("THE_USER_HAS_BEEN_FROZEN");
                    } else {
                        return new UsernamePasswordAuthenticationToken(password, passwordEncoder, userDetails.getAuthorities());
                    }
                } else {
                    throw new BadCredentialsException("INCORRECT_USERNAME_OR_PASSWORD");
                }
            }
            // 前置验证函数，根据用户名和密码判断是否通过验证
            @Override
            public boolean supports(Class<?> authentication) {
                return authentication.equals(UsernamePasswordAuthenticationToken.class);
            }
        };
    }
}
